package util.methods;

import java.util.Properties;
import java.util.Enumeration;
import java.util.Vector;
import java.util.TreeSet;
import java.util.Set;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileReader;

import cmp.main.Globals;
import assem.Log;
import assem.enums.LogLevel;
import jpave.annotator.CoreMain;

// CAS 2/7/15 replace PAVE.cfg with sTCW.cfg
// Parse and store the sTCW.cfg and LIB.cfg properties. 
// Needs to be updated to also read jpave.config (right now read in DatabaseData.java

public class PaveProps
{
	private static Properties mProps = null;
	private static Properties mLibPropsDef = null;
	private PropType mType;
	public Vector<Properties> mLibProps = null;
	public TreeSet<String> mUserKeys; // keys actually set by the user

	public PaveProps(PropType type)
	{
		mType = type;
		mUserKeys = new TreeSet<String>();

		if (mProps == null)
		{
			mProps = new Properties();

			switch (mType)
			{
			case Assem:
				// set defaults for all possible properties so we can flag unknown properties in the cfg file
				// except for TCNN...
				
				// these are also used by annotator
				mProps.setProperty("sTCW_db", "");
				mProps.setProperty("PAVE_db", "");
				mProps.setProperty("PAVE_host", "");
				mProps.setProperty("PAVE_user", "");
				mProps.setProperty("PAVE_password", "");	
				mProps.setProperty("ID", "");
				mProps.setProperty("AssemblyID", "");
				mProps.setProperty("CPUs", "1");
				mProps.setProperty("DEBUG", "0");
				
				mProps.setProperty("ALLOW_REV_BURIES","1");
				mProps.setProperty("BASECALL_ERROR_RATE", ".005");
				mProps.setProperty("BLAST_BURY_MISMATCH", "3");
				mProps.setProperty("BLAST_NATIVE_THREADING","0");
				mProps.setProperty("BLAST_TYPE", "Megablast");
				mProps.setProperty("BURY_BLAST_EVAL","1e-50");
				mProps.setProperty("BURY_BLAST_IDENTITY","99");
				mProps.setProperty("CAP_ARGS","-p 85 -y 70 -b 80 -o 49 -t 10000");
				mProps.setProperty("CAP_CMD", getExtDir() + "/CAP3/cap3");
				mProps.setProperty("CAP_BURY_MIN_DEPTH","5");
				mProps.setProperty("CAP_BURY_MAX_HANG","10");
				mProps.setProperty("CLIQUE", "100 98 20");
				mProps.setProperty("CLIQUE_BLAST_EVAL","1e-20");
				//mProps.setProperty("CLIQUE_BLAST_PARAMS"," -FF ");
				mProps.setProperty("CLIQUE_SIZE_SING", "2");
				mProps.setProperty("CLIQUE_SIZE_PAIR", "4");
				mProps.setProperty("DO_INITIAL_BURY","1");
				mProps.setProperty("DO_CAP_BURY","1");
				mProps.setProperty("DO_RECAP","1");
				mProps.setProperty("DO_CLIQUES","1");
				mProps.setProperty("EXTRA_CONFIRM","2");
				mProps.setProperty("EXTRA_RATE",".005");				
				mProps.setProperty("EXTRA_SCORE",".001");				
				//mProps.setProperty("FDB_CMD","formatdb");
				mProps.setProperty("FDB_ARGS","-pF -oT");
				mProps.setProperty("HEURISTICS", "1");
				mProps.setProperty("IGNORE_HPOLY","3");
				mProps.setProperty("INDEL_CONFIRM","2");
				mProps.setProperty("libraries", "");				
				mProps.setProperty("MAX_CLIQUE_SIZE_SING", "600");
				mProps.setProperty("MAX_CLIQUE_SIZE_PAIR", "400");				
				mProps.setProperty("MAX_CAP_SIZE", "1500");				
				mProps.setProperty("MAX_MERGE_LEN","0");
				mProps.setProperty("MIN_UNBURIED","0");
				mProps.setProperty("NO_TEST4", "0");			
				mProps.setProperty("POOR_ALIGN_PCT","97"); 
				mProps.setProperty("RECAP_ARGS","-p 70 -y 70 -b 80 -o 49 -t 10000");
				mProps.setProperty("REQUIRE_TWO_BRIDGE", "0");
				mProps.setProperty("SELF_JOIN", "50 97 20");
				//mProps.setProperty("SAVE_BLASTS", "0");
				mProps.setProperty("SKIP_ASSEMBLY", "1");
				mProps.setProperty("SNP_CONFIRM","2");
				mProps.setProperty("SNP_SCORE",".001");
				mProps.setProperty("TC1","200 98 20");
				mProps.setProperty("TC2","200 98 20");
				mProps.setProperty("TC3","150 97 20");
				mProps.setProperty("TC4","150 97 20");
				mProps.setProperty("TC5","100 97 20");
				mProps.setProperty("TC6","100 97 20");
				mProps.setProperty("TC_BLAST_EVAL","1e-20");
				mProps.setProperty("TEST_CTG","");
				mProps.setProperty("UNBURY_SNP","1");
				mProps.setProperty("USE_TRANS_NAME","0");				
				mProps.setProperty("User_EST_selfblast", "");
				
				break;
				
			case Annotate:
				mProps.setProperty("PAVE_db", ""); // old
				mProps.setProperty("PAVE_host", "");
				mProps.setProperty("PAVE_user", "");
				mProps.setProperty("PAVE_password", "");	
				mProps.setProperty("AssemblyID", ""); //old
				mProps.setProperty("CPUs", "1");							
				mProps.setProperty("JPAVE_restart", "-");        // depreciated
				mProps.setProperty("JPAVE_unitrans_type", "-1"); // 0 all, 1 ctg, 2 sing
				
				mProps.setProperty("JPAVE_flanking_region", "-1"); // not documented

				mProps.setProperty("JPAVE_uniprot_blast", "-"); // pre-v2.6
				mProps.setProperty("JPAVE_uniprot_fasta", "-"); // pre-v2.6
				mProps.setProperty("JPAVE_uniprot_args", "");   // pre-v2.6
				mProps.setProperty("JPAVE_unitrans_subset", "no"); // v3.0
				mProps.setProperty("JPAVE_uniprot_taxo", "-");     // v3.0
				mProps.setProperty("JPAVE_DBdate", "-");     // v3.0
				mProps.setProperty("JPAVE_min_bitscore", "-1"); // v3.0
				
				for (int i=1; i<CoreMain.numDB; i++) {
					mProps.setProperty("JPAVE_unitrans_DBblast_" + Integer.toString(i), "-");
					mProps.setProperty("JPAVE_DBfasta_" + Integer.toString(i), "-");
					mProps.setProperty("JPAVE_DBtaxo_" + Integer.toString(i), "-");
					mProps.setProperty("JPAVE_DBargs_" + Integer.toString(i), "");
					mProps.setProperty("JPAVE_unitrans_subset_" + Integer.toString(i), "-");
					mProps.setProperty("JPAVE_DBsearch_pgm_" + Integer.toString(i), "-");
					mProps.setProperty("JPAVE_DBdate_" + Integer.toString(i), "-");
				}
				
				mProps.setProperty("JPAVE_pairs_limit", "-1");
				mProps.setProperty("JPAVE_unitrans_selfblast", "-");
				mProps.setProperty("JPAVE_selfblast_args", "");
				mProps.setProperty("JPAVE_unitrans_tselfblast", "-");
				mProps.setProperty("JPAVE_tselfblast_args", "");
				mProps.setProperty("JPAVE_rep_overlap", "-1");
				mProps.setProperty("JPAVE_rep_similarity", "-1");

				mProps.setProperty("JPAVE_peptide_fasta", ""); 
				mProps.setProperty("JPAVE_GO_DB", ""); 
				mProps.setProperty("JPAVE_SKIP_ANNOTATE", "0"); 
				break;		
			
			case Cmp:
				mProps.setProperty("CPAVE_db", "");
				mProps.setProperty("CPAVE_host", "");
				mProps.setProperty("CPAVE_blast", "");
				mProps.setProperty("CPAVE_blast_params", "");
				mProps.setProperty("CPAVE_use_filter", "");
				mProps.setProperty("CPAVE_blast_filter", "");
				mProps.setProperty("CPAVE_blast_ident", "");
				mProps.setProperty("CPAVE_blast_maxnonalign", "");
				mProps.setProperty("CPAVE_GO_DB", "go_apr11");
				
				for (int i=1; i<Globals.CompilePanel.numDB; i++) {
					mProps.setProperty("PAVE_db" + Integer.toString(i), "-");
					mProps.setProperty("PAVE_assem" + Integer.toString(i), "-");
					mProps.setProperty("PAVE_host" + Integer.toString(i), "-");
					mProps.setProperty("PAVE_AAFile" + Integer.toString(i), "-");
					mProps.setProperty("PAVE_SMATFile" + Integer.toString(i), "-");
					mProps.setProperty("PAVE_remark" + Integer.toString(i), "-");
					mProps.setProperty("PAVE_prefix" + Integer.toString(i), "-");
				}
				for (int i=1; i<Globals.CompilePanel.numDB; i++) {
					mProps.setProperty("POG_method_name"+ Integer.toString(i), "");
					mProps.setProperty("POG_method_type"+ Integer.toString(i), "");
					mProps.setProperty("POG_method_prefix"+ Integer.toString(i), "");
					mProps.setProperty("POG_file"+ Integer.toString(i), "");
					mProps.setProperty("POG_comment"+ Integer.toString(i), "");
					mProps.setProperty("POG_setting"+ Integer.toString(i), "");
				}
				break;
			case Lib:
				mProps.setProperty("PAVE_db", "");
				mProps.setProperty("PAVE_host", "");
				mProps.setProperty("PAVE_user", "");
				mProps.setProperty("PAVE_password", "");	
				
				// Must also add to the list in Library.java!
				mLibPropsDef = new Properties();
				mLibPropsDef.setProperty("MIN_SEQ_LEN", "50");
				mLibPropsDef.setProperty("fiveprimesuf", ".f");
				mLibPropsDef.setProperty("threeprimesuf", ".r");		
				mLibPropsDef.setProperty("libid", "");				
				mLibPropsDef.setProperty("translib", "");				
				mLibPropsDef.setProperty("ctglib", "0");				
				mLibPropsDef.setProperty("reps", "");				
				mLibPropsDef.setProperty("title", "");				
				mLibPropsDef.setProperty("seqfile", "");				
				mLibPropsDef.setProperty("qualfile", "");				
				mLibPropsDef.setProperty("expfile", "");				
				mLibPropsDef.setProperty("source", "");				
				mLibPropsDef.setProperty("organism", "");				
				mLibPropsDef.setProperty("cultivar", "");				
				mLibPropsDef.setProperty("strain", "");				
				mLibPropsDef.setProperty("tissue", "");				
				mLibPropsDef.setProperty("stage", "");				
				mLibPropsDef.setProperty("treatment", "");				
				mLibPropsDef.setProperty("year", "");				
				mLibPropsDef.setProperty("sourcelink", "");				
				//mLibPropsDef.setProperty("default_qual", "18");
				mLibPropsDef.setProperty("prefix", "");
				mProps.setProperty("DEBUG", "0");
				break;
			}		
		}
	}
	public boolean dbPropsSet() throws Exception
	{
		return !(getProperty("PAVE_host").equals("") &&
				getProperty("PAVE_user").equals("") && getProperty("PAVE_password").equals(""));
	}
	public void importProps(PaveProps props) throws Exception
	{
		for (Object key : props.keySet())
		{
			mProps.setProperty(key.toString(), props.getProperty(key.toString()));
		}
	}
	public PaveProps(Properties defaults)
	{
		mProps = new Properties(defaults);
	}

	public String getProperty(String key) 
	{
		try
		{
			if (mProps.containsKey(key))
			{
				return mProps.getProperty(key);
			}
		}
		catch(Exception e)
		{
			System.out.println("Warning: check for nonexistent config property " + key);
		}
		return "";
	}

	public void setProperty(String key, String val)
	{
		mProps.setProperty(key, val);
	}

	public boolean containsKey(String key)
	{
		return mProps.containsKey(key);
	}
	public String fixCase(String in) throws Exception
	{
		String out = in;
		if (in.startsWith("tc") || in.startsWith("Tc") || in.startsWith("tC") || in.startsWith("TC")) 
		{
			out = in.toUpperCase();
		}
		else
		{
			for (Object keyObj : mProps.keySet())
			{
				String key = keyObj.toString();
				if (in.equalsIgnoreCase(key))
				{
					out = key;
					break;
				}
			}
		}
		return out;
	}
	/*
	 * Load sTCW.cfg for assembly and library
	 */
	
	private void clearTCs()
	{
		for (int i = 1; i <= 100; i++)
		{
			String key = "TC" + i;
			if (mProps.containsKey(key))
			{
				mProps.remove(key);
			}
		}
	}
	public void load(File f) throws Exception
	{
		BufferedReader br = new BufferedReader(new FileReader(f));
		boolean foundTC = false;
		while (br.ready())
		{	
			String line = br.readLine();
			line = line.replaceAll("#.*","");
			line += " "; // so the split works right 
			String[] fields = line.split("=");
			if (fields.length == 2)
			{
				String key = fields[0].trim();
				String val = fields[1].trim();
				String keyFixedCase = fixCase(key);
				if (key.toLowerCase().startsWith("count_")) continue;
				if (keyFixedCase.startsWith("TC"))
				{
					boolean match = false;
					// Note several params start with TC
					for (int i = 1; i <= 100; i++)
					{
						String tck = "TC" + i;
						if (keyFixedCase.equals(tck))
						{
							match=true; break;
						}
					}
					if (match)
					{
						// Found a TC entry, hence clear out the 6 defaulted ones
						if (!foundTC)
						{
							clearTCs();
							foundTC = true;
						}
					}
				}
				if (!key.equals(""))
				{					
					if (mProps.containsKey(keyFixedCase) || keyFixedCase.startsWith("TC"))
					{
						mProps.setProperty(keyFixedCase,val);
						mUserKeys.add(keyFixedCase);
					}
					else if (mType == PropType.Assem )
					{
						if (!key.toUpperCase().startsWith("JPAVE"))
						{
							Log.msg("*** sTCW.cfg: Ignoring unknown parameter:" + key, LogLevel.Basic);
						}
					}
					else if (mType == PropType.Annotate )
					{
						if (key.toUpperCase().startsWith("JPAVE"))
						{
							Log.msg("*** sTCW.cfg: Ignoring unknown parameter:" + key, LogLevel.Basic);
						}
					}					
					else if (mType == PropType.Lib && !mLibPropsDef.containsKey(keyFixedCase))
					{
						Log.msg("*** sTCW.cfg: Ignoring unknown parameter:" + key, LogLevel.Basic);
					}
					else if (mType == PropType.Cmp )
					{
						Log.msg("*** mTCW.cfg: Ignoring unknown parameter:" + key, LogLevel.Basic);
					}		
				}
			}
		}	
		fixProps(mProps);
		checkRequiredFields();
		if (mType == PropType.Lib)
		{
			loadLibProps(f);
			TreeSet<String> libIDs = new TreeSet<String>();
			for (Properties p : mLibProps)
			{
				fixProps(p);
				String libid = p.getProperty("libid");
				if (libIDs.contains(libid))
				{
					Log.die("Duplicate libid " + libid);
				}
				libIDs.add(libid);
			}
		}

	}

	/*
	 * Load sTCW.cfg for Annotator
	 */
	public void loadAnnotate(File f) throws Exception
	{	
		int error=0;
		BufferedReader br = new BufferedReader(new FileReader(f));
		while (br.ready())
		{	
			String line = br.readLine();
			line = line.replaceAll("#.*","");
			if (line.trim().length() == 0) continue;
			line += " ";
			String[] fields = line.split("=");
			if (fields.length == 2)
			{
				String key = fields[0].trim();
				String val = fields[1].trim();
				String keyFixedCase = fixCase(key);
				if (!key.equals(""))
				{					
					if (mProps.containsKey(keyFixedCase))
					{
						if (mUserKeys.contains(keyFixedCase)) {
							CoreMain.Print("***sTCW.cfg: duplicate parameter:  " + line);
							error++;
						}
						else {
							mProps.setProperty(keyFixedCase,val);
							mUserKeys.add(keyFixedCase);
						}
					}
					else if (key.toUpperCase().startsWith("JPAVE") || 
							 key.toUpperCase().startsWith("PAVE"))
					{
						CoreMain.Print("***sTCW.cfg: Ignoring unknown parameter:" + key);
						error++;
					}
				}
			}
			else {
				CoreMain.Print("***sTCW.cfg: incorrect line: " + line);
				error++;
			}
		}	
		if (error>0) {
			if (!CoreMain.yesNo("Continue? ")) CoreMain.die(" user terminated ");
		}
		fixProps(mProps);
		checkRequiredFields();
	}
	
	/*
	 * Load mTCW.cfg for cmpPAVE
	 * XXX Mark, I added this as it will be necessary, but its not tested
	 */
	public void loadCmp(File f) throws Exception
	{	
		try {			
			int error=0;
			String line = "";
			BufferedReader br = new BufferedReader(new FileReader(f));
			while ((line = br.readLine()) != null)
			{	
				line = line.replaceAll("#.*","");
				if (line.trim().length() == 0) continue;
				line += " ";
				String[] fields = line.split("=");
				
				if (fields.length == 2)
				{
					String key = fields[0].trim();
					String val = fields[1].trim();
					String keyFixedCase = fixCase(key);
					if (!key.equals(""))
					{		
						if (mProps.containsKey(keyFixedCase))
						{
							if (mUserKeys.contains(keyFixedCase)) {
								CoreMain.Print("***mTCW.cfg: duplicate parameter:  " + line);
								error++;
							}
							else {
								mProps.setProperty(keyFixedCase,val);
								mUserKeys.add(keyFixedCase);
							}
						}
						else if (key.toUpperCase().startsWith("CPAVE"))
						{
							CoreMain.Print("***mTCW.cfg: Ignoring unknown parameter:" + key);
							error++;
						}
					}
				}
				else {
					CoreMain.Print("***mTCW.cfg: incorrect line: " + line);
					error++;
				}
			}	
			if (error>0) {
				if (!CoreMain.yesNo("Continue? ")) CoreMain.die(" user terminated ");
			}
			br.close();
			fixProps(mProps);
			checkRequiredFields();
		} catch(Exception e) {
		    e.printStackTrace();
		}
	}


	public void checkRequiredFields() throws Exception
	{
		if (mType == PropType.Assem)
		{
			parseCapProps();
		}
		for (Enumeration<?> e = mProps.propertyNames(); e.hasMoreElements(); )
		{
			String propName = e.nextElement().toString();
			if (propName.contains("ARGS"))
			{
				String val = mProps.getProperty(propName);
				if (val.contains(">"))
				{
					Log.die("Configuration file property " + propName + " contains illegal character '>'");
				}
			}
		}
	}
	public void parseCapProps() throws Exception
	{
		String[] sjFields = mProps.getProperty("SELF_JOIN").trim().split("\\s+");
		if (sjFields.length != 3)
		{
			throw(new Exception("Wrong number of parameters in SELF_JOIN setting"));
		}
		mProps.setProperty("MIN_MATCH_SELF", sjFields[0]);
		mProps.setProperty("MIN_ID_SELF", sjFields[1]);
		mProps.setProperty("MAX_HANG_SELF", sjFields[2]);
		
		String[] cFields = mProps.getProperty("CLIQUE").trim().split("\\s+");
		if (cFields.length != 3)
		{
			throw(new Exception("Wrong number of parameters in CLIQUE setting"));
		}
		mProps.setProperty("MIN_MATCH_CLIQUE", cFields[0]);
		mProps.setProperty("MIN_ID_CLIQUE", cFields[1]);
		mProps.setProperty("MAX_HANG_CLIQUE", cFields[2]);		
	}
	public void fixProps(Properties props)
	{
		Enumeration<?> keys = props.propertyNames();
		while (keys.hasMoreElements())
		{
			String key = keys.nextElement().toString();
			String val = props.getProperty(key);
			val = val.replaceAll("#.*", "");
			props.setProperty(key, val.trim());
		}
	}

	public void printProperties()
	{
		dumpProps(mProps);
	}
	// Since we print the properties to the basic log as they are read in, this
	// just prints to the detail log now
	public void dumpProps(Properties props)
	{
		Log.indentMsg("Properties set in config file:\n",LogLevel.Basic);
		Enumeration<?> keys = props.propertyNames();
		while (keys.hasMoreElements())
		{
			String key = keys.nextElement().toString();
			if (!mUserKeys.contains(key)) continue;
			String val = props.getProperty(key);
			Log.indentMsg(key + " = " + val,LogLevel.Detail);
			key = key.replace("PAVE_", "");
			Log.indentMsg(key + " = " + val,LogLevel.Basic);
		}
		Log.msg("\nDefaulted properties:\n",LogLevel.Detail);
		keys = props.propertyNames();
		while (keys.hasMoreElements())
		{
			String key = keys.nextElement().toString();
			if (mUserKeys.contains(key)) continue;
			String val = props.getProperty(key);
			Log.indentMsg(key + ":" + val,LogLevel.Detail);
		}

		Log.msg("",LogLevel.Detail);
	}
	public Set<Object> keySet()
	{
		return mProps.keySet();	
	}
	// The lib sections duplicate parameter names so we have to parse separately
	// First break into separate strings, then use the standard property parse
	// on a stream made from the string
	public void loadLibProps(File f) throws Exception
	{
		Vector<String> libSecs = new Vector<String>();
		BufferedReader reader = new BufferedReader(new FileReader(f));
		int i = 0;
		libSecs.add(i, "");
		String line;

		while ((line = reader.readLine()) != null)
		{
			line = line.trim();
			if (line.toLowerCase().startsWith("libid") || line.toLowerCase().startsWith("translib") )
			{
				if (line.contains("__"))
				{
					Log.die("Library name should not contain '__':\n" + line );
				}
				i++;
				libSecs.add(i, "");
			}
			libSecs.set(i, libSecs.get(i) + line + "\n");
		}
		mLibProps = new Vector<Properties>();
		for (i = 1; i < libSecs.size(); i++)
		{
			mLibProps.add(i - 1, new Properties(mLibPropsDef));
			mLibProps.get(i - 1).load(new ByteArrayInputStream(libSecs.get(i).getBytes()));
			adjustLibProps(mLibProps.get(i-1));
		}
	}
	
	public void adjustLibProps(Properties p)
	{
		p.setProperty("orig_libid", p.getProperty("libid"));
		if (p.containsKey("translib"))
		{
			p.setProperty("libid", p.getProperty("translib"));
			p.setProperty("ctglib","1");
		}
		else
		{
			if (p.containsKey("prefix"))
			{
				String pfx = p.getProperty("prefix").trim();
				p.setProperty("libid",pfx + p.getProperty("libid"));
			}
		}
	}
	public static boolean isMac() {
		return System.getProperty("os.name").toLowerCase().contains("mac");
	}
	public static String getExtDir()
	{
		if (isMac())
		{
			return "external_osx";
		}
		else
		{
			return "external";
		}
	}
}
